Skip to content

[refactor] migrate NGO pages from SSR to SSG#79

Merged
TechQuery merged 1 commit into
mainfrom
NGO-SSG
Jun 14, 2026
Merged

[refactor] migrate NGO pages from SSR to SSG#79
TechQuery merged 1 commit into
mainfrom
NGO-SSG

Conversation

@TechQuery

@TechQuery TechQuery commented Jun 14, 2026

Copy link
Copy Markdown
Member

PR-79 PR-79 PR-79 Powered by Pull Request Badge

  1. [refactor] migrate NGO pages from SSR to SSG (resolve 中国公益数据库 2.0 页面静态化 #78)
  2. [migrate] upgrade to PNPM 11, Lint-Staged 17 & other latest Upstream packages
  3. [optimize] upgrade GitHub-reward & Lark-GitHub-bot actions

Summary by CodeRabbit

发布说明

  • 性能优化

    • NGO目录首页及年度统计页面现采用静态生成方式,显著提升页面加载速度
  • 样式调整

    • 优化组织展览页面分组标题显示,防止标题文本换行
  • 改进

    • 完善文件接口的错误响应处理机制

[migrate] upgrade to PNPM 11, Lint-Staged 17 & other latest Upstream packages
[optimize] upgrade GitHub-reward & Lark-GitHub-bot actions
Copilot AI review requested due to automatic review settings June 14, 2026 23:00
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

/NGO 路由下三个页面(首页、年份统计页、Landscape 页)从 getServerSideProps 迁移至 getStaticPaths + getStaticProps 静态生成,统一移除 next-ssr-middleware,改为构建期调用 lark.getAccessToken() 注入客户端后拉取数据;同时修复 Lark 文件 API 的错误体解析逻辑,并为 Landscape 标题补加 text-nowrap

Changes

NGO 页面静态化 (SSR → SSG)

Layer / File(s) Summary
NGO 首页 getStaticProps 改造
pages/NGO/index.tsx
移除 next-ssr-middlewareInferGetServerSidePropsType,改为 getStaticProps:先调用 lark.getAccessToken() 并注入 organizationStore.client,再调用 getYearRange() 返回静态 props;组件类型参数同步更新为 InferGetStaticPropsType
年份统计页 getStaticPaths + getStaticProps
pages/NGO/[year]/index.tsx
新增 getStaticPaths 通过 OrganizationYearStatisticModel 拉取所有年份,fallback: 'blocking';新增 getStaticProps 获取令牌后构建 OrganizationModel 并按 startYear 拉取统计数据,异常时返回 notFound + revalidate
Landscape 页 getStaticPaths + getStaticProps
pages/NGO/[year]/landscape.tsx
同步重构为 SSG:getStaticPaths 构建期注入 lark.client 并枚举年份;getStaticProps 调用 groupAllByTypetypeMap 序列化后返回,异常时 console.error 并返回 notFound + revalidate
Lark 文件 API 错误体解析
pages/api/Lark/file/[id]/[name].ts
!ok 分支中以 parseJSON(await response.text()) 替代原 response.json() 回退 response.text() 逻辑,统一用 web-utilityparseJSON 处理。
Landscape 标题样式
components/Organization/Landscape.tsx
分组标题 <h2>className 补加 text-nowrap,防止标题在布局中换行。

估算代码审查工作量

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • Open-Source-Bazaar/Open-Source-Bazaar.github.io#63:将 BI-table 模型与统计字段迁移至 OrganizationModel,本 PR 的 SSG 重构在 getStaticProps 中直接依赖其引入的 getStatistic({ startYear })statistic.city/typeMap 数据结构。

Suggested Labels

enhancement

🌱 静态之风轻轻吹,
SSR 旧衣悄然褪,
Build 时令牌一并取,
页面飞速不再等。
text-nowrap 守护标题美,parseJSON 解体整齐归!✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题 '[refactor] migrate NGO pages from SSR to SSG' 准确反映了PR的核心改动:将NGO路由下的所有页面从服务端渲染迁移至静态生成。
Linked Issues check ✅ Passed PR已完整实现issue #78的目标:pages/NGO/index.tsx、pages/NGO/[year]/index.tsx、pages/NGO/[year]/landscape.tsx均从getServerSideProps迁移至getStaticPaths+getStaticProps的SSG方案。
Out of Scope Changes check ✅ Passed components/Organization/Landscape.tsx的文本换行优化及pages/api/Lark/file接口的错误处理改进均为相关支持性调整,与SSG迁移目标相关。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch NGO-SSG

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot

dosubot Bot commented Jun 14, 2026

Copy link
Copy Markdown

📄 Knowledge review

✏️ Documentation updates

1 page was updated by changes in this PR.

Page Library Status
CI/CD and Deployment Automation Open Source Bazaar's Space ✅ Updated
📝 CI/CD and Deployment Automation — changes
@@ -95,6 +95,7 @@
 
 Example fields in the reward task form:
 - Task description
+- Task source (URL from an external system, optional)
 - Reward currency (dropdown selection)
 - Reward amount
 - Reward payer (GitHub username, optional)

Leave Feedback Ask Dosu about Open-Source-Bazaar.github.io Add Dosu to your team

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/Organization/Landscape.tsx (1)

61-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

<ul> 元素缺少 key 属性

rows.map() 中渲染的 <ul> 元素没有 key 属性,这会导致 React 在协调时产生警告并可能影响渲染性能。

🐛 建议修改
-          <ul className={`list-unstyled d-flex flex-${screenNarrow ? 'column' : 'row'} gap-2`}>
+          <ul key={row.map(([name]) => name).join('-')} className={`list-unstyled d-flex flex-${screenNarrow ? 'column' : 'row'} gap-2`}>

或者使用索引作为 key(因为 rows 顺序稳定):

-        {rows.map(row => (
+        {rows.map((row, index) => (
-          <ul className={`list-unstyled d-flex flex-${screenNarrow ? 'column' : 'row'} gap-2`}>
+          <ul key={index} className={`list-unstyled d-flex flex-${screenNarrow ? 'column' : 'row'} gap-2`}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/Organization/Landscape.tsx` around lines 61 - 72, The `<ul>`
element rendered in the `rows.map()` callback is missing a required `key` prop,
which causes React reconciliation warnings and impacts rendering performance.
Add a `key` prop to the `<ul>` element in the `rows.map()` loop. Since the rows
array order is stable, use the index parameter from the map callback as the key
value.
🧹 Nitpick comments (5)
pages/NGO/index.tsx (2)

23-34: ⚡ Quick win

observer 包装可移除

组件内部未使用任何 MobX observable 状态,observer 高阶组件带来的额外订阅开销是不必要的。useContext(I18nContext) 返回的 t 函数是普通 React Context,不需要 MobX 响应式追踪。

♻️ 建议修改
-const OrganizationHomePage: FC<InferGetStaticPropsType<typeof getStaticProps>> = observer(props => {
+const OrganizationHomePage: FC<InferGetStaticPropsType<typeof getStaticProps>> = props => {
   const { t } = useContext(I18nContext);
 
   return (
     <Container className="py-5">
       <PageHead title={t('China_NGO_DB')} />
       <h1 className="text-center my-4">{t('China_NGO_DB')} 2.0</h1>
 
       <ZodiacBar {...props} itemOf={year => ({ title: year, link: `/NGO/${year}` })} />
     </Container>
   );
-});
+};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/NGO/index.tsx` around lines 23 - 34, The OrganizationHomePage component
is unnecessarily wrapped with the observer higher-order component from MobX,
which adds subscription overhead even though the component contains no MobX
observable state. The only state accessed is from useContext(I18nContext), which
is standard React Context and does not require MobX reactive tracking. Remove
the observer wrapper from the component declaration so it becomes a regular FC
without the MobX observer higher-order component.

12-21: 💤 Low value

建议添加 revalidate 以支持增量静态再生成(ISR)

当前 getStaticProps 成功路径未设置 revalidate,页面数据仅在构建时生成,后续不会自动更新。如果年份范围数据可能变化,建议添加 revalidate 以启用 ISR:

♻️ 建议修改
-  return { props: { startYear, endYear } };
+  return { props: { startYear, endYear }, revalidate: 60 * 60 }; // 每小时重新验证
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/NGO/index.tsx` around lines 12 - 21, The getStaticProps function in
pages/NGO/index.tsx is missing the revalidate property in its return statement,
which prevents Incremental Static Regeneration (ISR) from working. Add a
revalidate property to the object returned by getStaticProps to specify how
often the page should be regenerated in seconds. This will allow the page data,
including the year range retrieved from OrganizationModel.getYearRange(), to be
automatically updated at regular intervals instead of only at build time.
pages/NGO/[year]/index.tsx (2)

55-57: ⚡ Quick win

两个年份页面成功路径均缺少 revalidate,数据将永不自动更新。 当前实现仅在错误路径设置了 revalidate: Minute / Second,但成功生成的页面无法通过 ISR 自动刷新,导致数据一旦生成便永久静态化。

  • pages/NGO/[year]/index.tsx#L55-L57:在 return { props: { year, statistic } } 后添加 revalidate: Minute / Second
  • pages/NGO/[year]/landscape.tsx#L44-L46:在 return { props: JSON.parse(JSON.stringify({ typeMap })) } 后添加 revalidate: Minute / Second
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/NGO/`[year]/index.tsx around lines 55 - 57, The getStaticProps
functions in both affected files are missing the revalidate property in their
successful return paths, which prevents ISR automatic updates for the generated
pages. In pages/NGO/[year]/index.tsx at lines 55-57, add revalidate: Minute /
Second (or appropriate constant value) to the returned object after the props
property in the return statement. Similarly, in pages/NGO/[year]/landscape.tsx
at lines 44-46, add the same revalidate property to the returned object after
returning the typeMap props. This ensures that the successfully generated pages
will automatically refresh at the specified interval, matching the revalidate
configuration already present in the error paths.

65-88: 💤 Low value

observer 包装可移除

OrganizationPage 组件未直接观察任何 MobX observable 状态。statisticyear 都是从 props 获取的普通对象,移除 observer 可减少不必要的订阅开销。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/NGO/`[year]/index.tsx around lines 65 - 88, Remove the observer HOC
wrapper from the OrganizationPage component definition. The component does not
directly observe any MobX observable state, and both the year and statistic
props are plain objects rather than observables, making the observer wrapper
unnecessary. Simply unwrap the component function from the observer call in the
component assignment.
pages/NGO/[year]/landscape.tsx (1)

16-30: ⚖️ Poor tradeoff

getStaticPaths 逻辑与 [year]/index.tsx 重复

此处的年份获取逻辑与 pages/NGO/[year]/index.tsx 中的 getStaticPaths 完全相同。建议抽取为共享辅助函数以遵循 DRY 原则:

♻️ 建议抽取共享函数
// models/Organization.ts 或单独的 helpers 文件
export async function getNGOYearPaths() {
  await lark.getAccessToken();
  
  const yearStore = new OrganizationYearStatisticModel();
  yearStore.client = lark.client;
  
  const years = await yearStore.getAll();
  
  return years
    .map(({ name }) => name && { params: { year: name! } })
    .filter(Boolean) as { params: { year: string } }[];
}

然后在两个页面中:

export const getStaticPaths: GetStaticPaths<{ year: string }> = async () => ({
  paths: await getNGOYearPaths(),
  fallback: 'blocking',
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/NGO/`[year]/landscape.tsx around lines 16 - 30, The getStaticPaths
function in this file contains the same logic as another getStaticPaths
implementation: calling lark.getAccessToken, creating an
OrganizationYearStatisticModel instance, and calling getAll to retrieve and
transform years. Extract this duplicated logic into a shared helper function
(e.g., getNGOYearPaths) that handles the token retrieval, year store
initialization, and transformation, returning the formatted paths array. Then
refactor the getStaticPaths function in this file to call the helper function
instead of duplicating the inline logic. Apply this same refactoring to the
other file with identical getStaticPaths logic to eliminate code duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@components/Organization/Landscape.tsx`:
- Around line 61-72: The `<ul>` element rendered in the `rows.map()` callback is
missing a required `key` prop, which causes React reconciliation warnings and
impacts rendering performance. Add a `key` prop to the `<ul>` element in the
`rows.map()` loop. Since the rows array order is stable, use the index parameter
from the map callback as the key value.

---

Nitpick comments:
In `@pages/NGO/`[year]/index.tsx:
- Around line 55-57: The getStaticProps functions in both affected files are
missing the revalidate property in their successful return paths, which prevents
ISR automatic updates for the generated pages. In pages/NGO/[year]/index.tsx at
lines 55-57, add revalidate: Minute / Second (or appropriate constant value) to
the returned object after the props property in the return statement. Similarly,
in pages/NGO/[year]/landscape.tsx at lines 44-46, add the same revalidate
property to the returned object after returning the typeMap props. This ensures
that the successfully generated pages will automatically refresh at the
specified interval, matching the revalidate configuration already present in the
error paths.
- Around line 65-88: Remove the observer HOC wrapper from the OrganizationPage
component definition. The component does not directly observe any MobX
observable state, and both the year and statistic props are plain objects rather
than observables, making the observer wrapper unnecessary. Simply unwrap the
component function from the observer call in the component assignment.

In `@pages/NGO/`[year]/landscape.tsx:
- Around line 16-30: The getStaticPaths function in this file contains the same
logic as another getStaticPaths implementation: calling lark.getAccessToken,
creating an OrganizationYearStatisticModel instance, and calling getAll to
retrieve and transform years. Extract this duplicated logic into a shared helper
function (e.g., getNGOYearPaths) that handles the token retrieval, year store
initialization, and transformation, returning the formatted paths array. Then
refactor the getStaticPaths function in this file to call the helper function
instead of duplicating the inline logic. Apply this same refactoring to the
other file with identical getStaticPaths logic to eliminate code duplication.

In `@pages/NGO/index.tsx`:
- Around line 23-34: The OrganizationHomePage component is unnecessarily wrapped
with the observer higher-order component from MobX, which adds subscription
overhead even though the component contains no MobX observable state. The only
state accessed is from useContext(I18nContext), which is standard React Context
and does not require MobX reactive tracking. Remove the observer wrapper from
the component declaration so it becomes a regular FC without the MobX observer
higher-order component.
- Around line 12-21: The getStaticProps function in pages/NGO/index.tsx is
missing the revalidate property in its return statement, which prevents
Incremental Static Regeneration (ISR) from working. Add a revalidate property to
the object returned by getStaticProps to specify how often the page should be
regenerated in seconds. This will allow the page data, including the year range
retrieved from OrganizationModel.getYearRange(), to be automatically updated at
regular intervals instead of only at build time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2148c35b-af68-4be6-a927-68c57330260f

📥 Commits

Reviewing files that changed from the base of the PR and between 96f3b06 and c42e43c.

⛔ Files ignored due to path filters (10)
  • .github/ISSUE_TEMPLATE/reward-task.yml is excluded by none and included by none
  • .github/scripts/count-reward.ts is excluded by none and included by none
  • .github/scripts/share-reward.ts is excluded by none and included by none
  • .github/scripts/type.ts is excluded by none and included by none
  • .github/workflows/Lark-notification.yml is excluded by none and included by none
  • .github/workflows/claim-issue-reward.yml is excluded by none and included by none
  • .github/workflows/statistic-member-reward.yml is excluded by none and included by none
  • package.json is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml and included by none
  • pnpm-workspace.yaml is excluded by none and included by none
📒 Files selected for processing (5)
  • components/Organization/Landscape.tsx
  • pages/NGO/[year]/index.tsx
  • pages/NGO/[year]/landscape.tsx
  • pages/NGO/index.tsx
  • pages/api/Lark/file/[id]/[name].ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the /NGO route pages to use Static Site Generation (SSG) instead of SSR, while also upgrading dependencies and tightening/modernizing the GitHub reward automation workflows and scripts.

Changes:

  • Migrated NGO pages from getServerSideProps to getStaticProps/getStaticPaths with Lark-backed data fetching.
  • Updated reward-related GitHub Actions workflows and Deno scripts (including adding “source” metadata).
  • Introduced PNPM workspace config and bumped multiple upstream package versions.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Adds PNPM workspace + build allowlist config for the repo.
pages/NGO/index.tsx Switches NGO landing page to SSG.
pages/NGO/[year]/index.tsx Switches year detail page to SSG with static paths.
pages/NGO/[year]/landscape.tsx Switches year landscape page to SSG with static paths + ISR-style error fallback.
pages/api/Lark/file/[id]/[name].ts Refactors error-body parsing for Lark file download API.
package.json Upgrades dependencies and changes the test script behavior.
components/Organization/Landscape.tsx Adjusts layout classes to prevent wrapping in group titles.
.github/workflows/statistic-member-reward.yml Pins actions to SHAs and restricts Deno permissions for monthly reward stats.
.github/workflows/Lark-notification.yml Adjusts event serialization to pass GitHub context via env.
.github/workflows/claim-issue-reward.yml Adds concurrency/label gating, pins actions, restricts Deno permissions, and passes new “source” arg.
.github/scripts/type.ts Extends reward data model with optional source.
.github/scripts/share-reward.ts Refactors PR lookup query and reward tagging/pushing; adds source field.
.github/scripts/count-reward.ts Refines reward-tag date filtering and tag pushing behavior.
.github/ISSUE_TEMPLATE/reward-task.yml Adds form guidance and “Task source” input field.

Comment thread pages/NGO/index.tsx
Comment on lines +13 to +16
await lark.getAccessToken();

const organizationStore = new OrganizationModel();
organizationStore.client = lark.client;
Comment on lines +36 to +41
return {
paths: years.map(({ name }) => name && { params: { year: name! } }).filter(Boolean) as {
params: { year: string };
}[],
fallback: 'blocking',
};
Comment on lines +24 to +29
return {
paths: years.map(({ name }) => name && { params: { year: name! } }).filter(Boolean) as {
params: { year: string };
}[],
fallback: 'blocking',
};
Comment thread package.json
"build": "next build --webpack",
"start": "next start",
"test": "lint-staged && tsc --noEmit"
"test": "lint-staged && git add . && tsc --noEmit"
Comment on lines +39 to +40
deno --allow-run --allow-sys --allow-env --allow-read --allow-net=api.github.com \
.github/scripts/share-reward.ts \
Comment on lines +41 to +43
env:
GH_TOKEN: ${{ github.token }}
run: deno --allow-run --allow-sys --allow-env --allow-read --allow-net=api.github.com .github/scripts/count-reward.ts
Comment on lines +74 to +75
const allUsers = [author.login, ...assignees.map(({ login }) => login)].uniqueBy();

Comment on lines +1 to 5
import 'npm:array-unique-proposal';

import { components } from 'npm:@octokit/openapi-types';
import { $, argv, YAML } from 'npm:zx';

@TechQuery TechQuery merged commit 58126ca into main Jun 14, 2026
6 of 7 checks passed
@TechQuery TechQuery deleted the NGO-SSG branch June 14, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Some improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

中国公益数据库 2.0 页面静态化

2 participants